using System;
using System.Threading;
using System.Windows.Forms;
using System.Collections;
using SecuBat.Common;
using Css.Diagnostics;
using SecuBat.Logging;

namespace SecuBat.Threading {

	/// <summary>
	/// Summary description for ControllerBase.
	/// </summary>
	public abstract class ThreadControllerBase 
	{

		protected ThreadBase oBaseThread;
		protected ThreadStart dgateRun;
		protected Thread thd;

		/// <summary>
		/// Status data from the thread base
		/// </summary>
		public StatusTickArgs LastStatus 
		{
			get 
			{
				return oBaseThread.basedata.LastStatus;
			}
		}

		/// <summary>
		/// </summary>
		public ThreadControllerBase( ThreadBase oBaseThread ) 
		{
			this.oBaseThread = oBaseThread;

			/* hook up the controller to handle the events fired from the thread.
						 * Note that the controller will receive the events the normal way, i.e.
						 * the method will execute in the thread context of the caller, which is
						 * the worker thread.
						 */
			oBaseThread.eventStatusTick += new StatusTickHandler( this.FireStatusTick );

			return;
		}

		/// <summary>
		/// Thread class accessor.
		/// </summary>
		/// <returns>The thread class provided when the controller
		/// was created.</returns>
		public ThreadBase ControlledThread 
		{
			get
			{
				return oBaseThread;
			}
		}

								   
		/// <summary>
		/// Starts the thread.
		/// </summary>
		public void Start() 
		{
			dgateRun = new ThreadStart( oBaseThread.RunOuter );
			if( null != dgateRun ) 
			{
				thd = new Thread( dgateRun );
				thd.Start();
			}
			return;
		}

		/// <summary>
		/// Stop the running thread via the polite mechanism.
		/// </summary>
		/// <param name="nTimeoutMs"></param>
		/// <returns>true if the thread did stop within the
		/// specified interval.</returns>
		public bool Stop( int nTimeoutMs ) 
		{
			bool bThreadStopped = false;
			if( null != oBaseThread ) 
			{

				/* set signal to request the thread to stop */
				oBaseThread.basedata.signalStopRequest.Set();

				/* block until the thread acknowledges that it
								will stop normally.  Strictly, the thread is
								still running at this exact moment.  It is a 
								design contract that the thread only signals the
								acknowledge event as its last action. */
				if( oBaseThread.basedata.signalStopAck.WaitOne( nTimeoutMs, false ) ) 
				{
					bThreadStopped = true;
				}
			}
			return bThreadStopped;
		}

		/// <summary>
		/// Abort the running thread.
		/// </summary>
		/// <remarks>This method terminates the thread with malice.
		/// This method will block up to the timeout interval waiting
		/// for the thread to exit.
		/// </remarks>
		/// <param name="nTimeoutMs">Maximum time in ms to wait</param>
		public bool Abort( long nTimeoutMs ) 
		{
			bool bThreadAborted = false;
			if( null != thd ) 
			{

				/* try to abort the thread */
				thd.Abort();

				/* poll looking for a clean exit code 
								Threads can struggle to stay alive, so the
								timeout here is necessary to prevent the
								owner from being blocked by a misbehaved
								thread. 
								Note that the exit code is not checked here.
								This method does not care why the thread has stopped
								only that it is stopped.
								*/
				const int nPeriod = 100;
				long nStart, nNow;
				nStart = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
				do 
				{
					Thread.Sleep( nPeriod );
					bThreadAborted = ( oBaseThread.basedata.LastStatus.State == StatusTickArgs.States.Stopped );
					nNow = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
				} 
				while( (!bThreadAborted) && ( (nNow - nStart) < nTimeoutMs ) );
			}
			return bThreadAborted;
		}

		/// <summary>
		/// Terminate thread by all means available.
		/// </summary>
		/// <remarks>Tries to Stop the thread then Abort.  Use
		/// this method when the caller does not care how the 
		/// thread was stopped, just that it is stopped.
		/// </remarks>
		/// <param name="nTimeoutMs"></param>
		/// <returns></returns>
		public bool Terminate( int nTimeoutMs ) 
		{
			bool bDead = false;

			if( Stop( nTimeoutMs ) ) 
			{
				bDead = true;
			}
			else if( Abort( nTimeoutMs ) ) 
			{
				bDead = true;
			}

			return bDead;
		}


		abstract public void FireStatusTick(Object sender, StatusTickArgs args );


		static protected void DoEvent( Delegate dgate, Object sender, StatusTickArgs args ) 
		{
			try 
			{
				if (dgate != null)
					dgate.DynamicInvoke(new object[]{sender, args } );
			}
			catch( Exception ex) 
			{
				LogManager.Write(TraceLevel.Warning, ex, "Threading");
			}
			return;
		}
	}

	/// <summary>
	/// 
	/// </summary>
	/// <remarks>
	/// Use this controller when there is only one listener or sink
	/// of status events.  A single sink is the common model, where
	/// a single form creates and listens to the worker thread.  
	/// </remarks>
	public class ThreadController : ThreadControllerBase 
	{

		public class MyData : SharedData 
		{
			public StatusTickHandler dgateOwner = null;
		}

		protected MyData data = new MyData();

		public ThreadController( ThreadBase oBaseThread ) : base( oBaseThread ) 
		{
			return;
		}

		/// <summary>Attach the control to the worker thread.</summary>
		/// <remarks>
		/// Sets the control and delegate to receive events from
		/// the worker thread.  Note that the Delegate <b>must</b> be
		/// created in the same thread as the Control.
		/// Controls are free to attach and detach from the worker at
		/// any time.
		/// </remarks>
		/// <param name="dgate"></param>
		/// <exception cref="System.ApplicationException">
		/// Thrown if the data
		/// cannot be set because the worker thread has locked it.  This
		/// could only happen if the thread was stalled or crashed 
		/// without releasing the lock.
		/// </exception>
		public void SetListener(StatusTickHandler dgate ) 
		{
			data.WriteLock();
			data.dgateOwner = dgate;
			data.WriteRelease();
			return;
		}


		/// <summary>Detach the listener from the worker thread.</summary>
		/// <exception cref="System.ApplicationException">
		/// Thrown if the data
		/// cannot be changed because the worker thread has locked it.  This
		/// could only happen if the thread was stalled or crashed 
		/// without releasing the lock.
		/// </exception>
		public void ClearListener() 
		{
			data.WriteLock();
			data.dgateOwner = null;
			data.WriteRelease();
			return;
		}

		/// <summary>Fire status event to any listeners.</summary>
		/// <remarks>Note: it is safe to call this regardless of whether any listeners have
		/// been attached.</remarks>
		/// <param name="args"></param>
		override public void FireStatusTick(Object sender, StatusTickArgs args) 
		{
			try 
			{
				data.ReadLock();
				if(null != data.dgateOwner)  
				{
					DoEvent( data.dgateOwner, sender, args ); 
				}
				data.ReadRelease();

				/* This yields to other threads.  Because messages
							where just posted into the listeners queues, they will
							be available to run.  If you set breakpoints, you may
							see that the Delegates are executed immediately
							when BeginInvoke is called.  This is reasonable because
							the worker has been getting all the time slices
							lately.  If the Delegates haven't executed yet,
							this call will make it likely that this 
							thread will block here and the listeners will run. */
				Thread.Sleep(0);
			}
			catch( ApplicationException ) 
			{
			}
			return;
		}
	}

	

	/// <summary>
	/// 
	/// </summary>
	/// <remarks>
	/// </remarks>
//	public class ControllerMultiSink : ControllerBase {
//
//        protected struct Listener {
//            public Listener( Control f, StatusTickHandler d ) {
//                form=f;
//                dgate=d;
//            }
//
//            public Control form;
//            public StatusTickHandler dgate;
//
//            /* matching rule 
//             * form must be equal
//             * null dgate matches any
//             */
//            public override bool Equals( object other ) {
//                bool bMatch = false;
//                if( form.Equals( ((Listener)other).form ) ) {
//                    if( (dgate != null) && (((Listener)other).dgate != null) ) {
//                        if( ((Listener)other).dgate == dgate ) {
//                            bMatch = true;
//                        }
//                    }
//                    else {
//                        bMatch = true;
//                    }
//                }
//                return bMatch;
//            }
//
//            /// <summary>
//            /// Compiler desires this override
//            /// </summary>
//            /// <returns></returns>
//            public override int GetHashCode() {
//                return base.GetHashCode();
//            }
//        }
//
//		public class MyData : SharedData {
//			public ArrayList listSinks;
//
//			public MyData() {
//				listSinks = new ArrayList();
//				return;
//			}
//
//		}
//
//		protected MyData data = new MyData();
//
//		public ControllerMultiSink( ThreadBase oBaseThread ) : base( oBaseThread ) {
//			return;
//		}
//
//		public void addListener( Control form, StatusTickHandler dgate ) {
//			try {
//				data.WriteLock();
//				data.listSinks.Add( new Listener( form, dgate ) );
//				data.WriteRelease();
//			}
//			catch( ApplicationException ) {
//			}
//			return;
//		}
//
//        /// <summary>
//        /// 
//        /// </summary>
//        /// <param name="form">Form that wants to detach</param>
//        /// <param name="dgate">If null, matches all delegates</param>
//		public void removeListener( Control form, StatusTickHandler dgate ) {
//			try {
//				data.WriteLock();
//                /* search for all matches and remove
//                Because the null delegate acts as a wildcard, more
//                than one match may occur */
//                Listener match = new Listener( form, dgate );
//                int nIndex;
//                do {
//                    nIndex = data.listSinks.IndexOf( match );
//                    if( -1 != nIndex ) {
//                        data.listSinks.RemoveAt( nIndex );
//                    }
//                } while( -1 != nIndex );
//
//				data.WriteRelease();
//			}
//			catch( ApplicationException ) {
//			}
//			return;
//		}
//
//		/// <summary>
//		/// Fire events into listener threads.
//		/// </summary>
//		/// <remarks>
//		/// This method translates the execution of the event
//		/// handler into the listener threads.  The same delegates
//		/// are executed, but in the context of the listener thread.
//		/// In C++, this is similar to a PostMessage from a worker thread
//		/// in that a message is added to the windows message queue. 
//		/// </remarks>
//		/// <param name="args"></param>
//		override public void FireStatusTick( StatusTickArgs args ) {
//            Listener l;
//			try {
//				data.ReadLock();
//				System.Collections.IEnumerator it = data.listSinks.GetEnumerator();
//				while ( it.MoveNext() ) {
//					l = (Listener)it.Current;
//					DoEvent( l.form, l.dgate, args ); 
//				}
//				data.ReadRelease();
//
//                /* This yields to other threads.  Because messages
//                where just posted into the listeners queues, they will
//                be available to run.  Thereby, this call will make
//                it likely that this thread will block here and
//                the listeners will run. */
//                Thread.Sleep(0);
//			}
//			catch( ApplicationException ) {
//			}
//
//			return;
//		}
//
//	}
//
}
